const ATTR_DRAG = '.dnd-draggable'; // 选择器
const ATTR_CONTEXT = '.dnd-context';
const ATTR_DROP = '.dnd-droppable';

let context; // 容器上下文
let allDrops; // 所有列dom
let indexedDrags; // 分组拖动dom
let dragItem; // 正在拖动的dom
let placeholder; // 占位dom
let dropIndex; // 当前over的列dom index
let dragIndex; // 当前over的拖动dom index
let originDropIndex; // 初始列index
let originDragIndex; // 初始拖动dom的index
let targetDom; // 初始拖动dom
let moveable = false; // 可拖动
let prevDropIndex; // 前一个有效dropIndex
const events = {}; // 挂载callback事件
const mousePos = {}; // 鼠标位置

const dropProps = []; // 缓存列组属性
const dragProps = []; // 缓存拖动组属性
const prevDragProps = []; // 拖动前的context内容如果有所变化，需要缓存之前的拖动dom位置用以调整
const adjust = { x: 0, y: 0 }; // 拖动偏移量
// 占位元素的附加样式
const placeholderStyle = {
    background: 'rgba(0, 0, 0, 0.08)',
    color: 'rgba(0,0,0,0.25)'
};
const OFFSET_Y = 68; // 拖动物件height + marginBottom，暂时写死

let validMousedown = false; // 有效的拖动初始状态
let validMouseMove = false; // 有效的拖动状态

// 封装一下写style方法
const setStyle = (dom, opt) => {
    Object.keys(opt).forEach(key => {
        // eslint-disable-next-line no-param-reassign
        dom.style[key] = opt[key];
    });
};

// 兼容一下ie...
function removeNode(dom) {
    if (typeof dom.removeNode === 'function') {
        dom.removeNode(true);
    } else {
        dom.remove();
    }
}

// 缓存drag dom
const setIndexedDrags = () => {
    indexedDrags = [];
    allDrops.forEach(drop => {
        const tmpAry = [];
        Array.from(drop.querySelectorAll(ATTR_DRAG)).forEach(drag => tmpAry.push(drag));
        indexedDrags.push(tmpAry);
    });
};

// 缓存drop dom
const setDragItemsDoms = () => {
    allDrops = Array.from(context.querySelectorAll(ATTR_DROP));
    setIndexedDrags();
};

// 缓存drag props
const setDragItemsProps = (isPrev = false) => {
    // clear
    dropProps.length = 0;
    dragProps.length = 0;
    if (isPrev) prevDragProps.length = 0;
    allDrops.forEach((drop, index) => {
        const dropOffset = drop.getBoundingClientRect();
        dropProps.push({ x: dropOffset.left, y: dropOffset.top, w: drop.offsetWidth, h: drop.offsetHeight });
        const tmpAry = [];
        indexedDrags[index].forEach(drag => {
            const dragOffset = drag.getBoundingClientRect();
            tmpAry.push({
                x: dragOffset.left,
                y: dragOffset.top,
                w: drag.offsetWidth,
                h: drag.offsetHeight
            });
        });
        if (!isPrev) {
            dragProps.push(tmpAry);
        } else {
            prevDragProps.push(tmpAry);
        }
    });
};

// 缓存数据
const setAllData = (isPrev = false) => {
    setDragItemsDoms();
    setDragItemsProps(isPrev);
};

// 获取点击dom，为了事件代理
const clickedDragItem = clickedDom => {
    let result = null;
    indexedDrags.some((drop, i) =>
        drop.some((drag, j) => {
            if (drag.contains(clickedDom)) {
                originDropIndex = i;
                originDragIndex = j;
                result = drag;
                return true;
            }
            return false;
        })
    );
    return result;
};

// 判断是否在列内
const getDropIndex = (x, y) => dropProps.findIndex(props => x > props.x && x < props.x + props.w && y > props.y);

// 判断是否在drag item上
const getDragIndex = (x, y) => {
    const curDragsProps = dragProps[dropIndex];

    // 保险？
    if (!curDragsProps) {
        return 0;
    }
    const last = curDragsProps[curDragsProps.length - 1];

    if (!curDragsProps.length) {
        return 0;
    }

    if (x > last.x && x < last.x + last.w && y > last.y + last.h) {
        return curDragsProps.length;
    }
    return curDragsProps.findIndex(
        props => x > props.x && x < props.x + props.w && y > props.y && y < props.y + props.h
    );
};

// 鼠标点击事件
const mouseDownFn = e => {
    setAllData();
    const { clientX: x, clientY: y } = e;
    const clickedDom = e.target;
    targetDom = clickedDragItem(clickedDom);
    if (targetDom) {
        if (typeof events.dragStart === 'function') events.dragStart();
        moveable = true;
        dragItem = targetDom.cloneNode(true);
        placeholder = targetDom.cloneNode(true);
        targetDom.parentNode.appendChild(dragItem);
        targetDom.parentNode.appendChild(placeholder);
        const targetDomOffset = targetDom.getBoundingClientRect();
        adjust.x = x - targetDomOffset.left;
        adjust.y = y - targetDomOffset.top;

        // 处理drag后context内容变化
        if (prevDragProps.length) {
            const prevTargetProps = prevDragProps[originDropIndex][originDragIndex];
            const curTargetProps = dragProps[originDropIndex][originDragIndex];
            // 暂时只处理y轴
            adjust.y += curTargetProps.y - prevTargetProps.y;
        }
        setStyle(dragItem, {
            position: 'fixed',
            zIndex: 1000,
            width: `${targetDom.offsetWidth}px`,
            cursor: 'move',
            left: `${x - adjust.x}px`,
            top: `${y - adjust.y}px`
        });
        setStyle(placeholder, {
            position: 'fixed',
            zIndex: 999,
            display: 'none',
            ...placeholderStyle
        });
    }
};

// 当是有效点击时
const handleAfterMouseDown = e => {
    if (typeof events.promiseDragStart === 'function') {
        // 处理context变更的点击事件
        // 写这个库就为了这里😂
        setAllData(true);
        const clickedDom = e.target;
        targetDom = clickedDragItem(clickedDom);
        if (!targetDom) return;
        events.promiseDragStart().then(() => {
            mouseDownFn(e);
        });
        return;
    }
    mouseDownFn(e);
};

// 处理点击，只做初始点击位置记录，用于判断是点击还是拖拽
const handleMousedown = e => {
    validMousedown = false;
    validMouseMove = true;
    const { clientX: x, clientY: y } = e;
    mousePos.x = x;
    mousePos.y = y;
};

// 鼠标拖动事件
const handleMousemove = e => {
    const { clientX: x, clientY: y } = e;

    if (validMouseMove) {
        // 区别是click还是拖拽
        if (!validMousedown && (Math.abs(x - mousePos.x) > 1 || Math.abs(y - mousePos.y) > 1)) {
            validMousedown = true;
            handleAfterMouseDown(e);
        }
    }

    // 处理真正的拖拽
    if (!moveable) return;
    setStyle(targetDom, {
        display: 'none'
    });
    setStyle(dragItem, {
        left: `${x - adjust.x}px`,
        top: `${y - adjust.y}px`
    });
    dropIndex = getDropIndex(x, y);
    if (dropIndex !== -1) {
        dragIndex = getDragIndex(x, y);
        if (dragIndex !== -1) {
            // 设置占位dom位置
            const curDragsProps = dragProps[dropIndex];

            let destPos;
            if (!curDragsProps.length) {
                // 空行
                destPos = { ...dropProps[dropIndex] };
            } else if (dragIndex === curDragsProps.length) {
                // 最后的位置
                destPos = { ...curDragsProps[dragIndex - 1] };
                if (dropIndex !== originDropIndex) destPos.y += OFFSET_Y;
            } else {
                // 其他的位置
                destPos = { ...curDragsProps[dragIndex] };
            }
            setStyle(placeholder, {
                width: `${destPos.w}px`,
                left: `${destPos.x}px`,
                top: `${destPos.y}px`,
                display: 'block'
            });
            indexedDrags.forEach((drop, i) => {
                drop.forEach((drag, j) => {
                    // 这判断能否优化？🤔
                    // 跨列
                    if (dropIndex !== originDropIndex) {
                        if (dropIndex === i) {
                            setStyle(drag, {
                                transform: `translateY(${j >= dragIndex ? OFFSET_Y : 0}px)`
                            });
                        } else if (prevDropIndex === i) {
                            setStyle(drag, {
                                transform: 'translateY(0)'
                            });
                        }
                    }
                    // 同一列
                    if (dropIndex === originDropIndex) {
                        if (dropIndex === i) {
                            let offsetY = 0;
                            if (originDragIndex >= dragIndex && j >= dragIndex) {
                                offsetY = OFFSET_Y;
                            }
                            if (originDragIndex < dragIndex && j > dragIndex) {
                                offsetY = OFFSET_Y;
                            }
                            setStyle(drag, {
                                transform: `translateY(${offsetY}px)`
                            });
                        } else if (prevDropIndex === i) {
                            setStyle(drag, {
                                transform: 'translateY(0)'
                            });
                        }
                    }
                });
            });
            // console.log(dragIndex, dropIndex, originDropIndex, originDragIndex);
        }
        // 用于清除多次跨列的影响
        prevDropIndex = dropIndex;
    }
};

// 鼠标松开事件
const handleMouseup = () => {
    validMouseMove = false;

    if (!moveable) return;
    moveable = false;

    if (dragItem) removeNode(dragItem);
    if (placeholder) removeNode(placeholder);
    if (targetDom) {
        setStyle(targetDom, {
            display: 'block'
        });
    }
    // 超出列边界时，以最后一个有效的列为准
    const destDropIndex = dropIndex === -1 ? prevDropIndex : dropIndex;

    // 清除drags的位移状态，只需要清除目标组的translate（不清除会影响下次拖拽），来源组是经过display:none处理
    Array.from(allDrops[destDropIndex].querySelectorAll(ATTR_DRAG)).forEach(drag => {
        setStyle(drag, {
            transform: 'translateY(0)'
        });
    });
    if (typeof events.dragEnd === 'function') {
        events.dragEnd({
            source: {
                dragIndex: originDragIndex,
                dropIndex: originDropIndex
            },
            destination: {
                dragIndex,
                dropIndex: destDropIndex // 当超出边界时
            }
        });
    }
};

// 销毁事件listener
const destroy = () => {
    if (!context) return;
    context.removeEventListener('mousedown', handleMousedown);
    context.removeEventListener('mousemove', handleMousemove);
    context.removeEventListener('mouseup', handleMouseup);
};

// 初始化
const initialize = () => {
    context = document.querySelector(ATTR_CONTEXT);
    if (!context) return;
    context.addEventListener('mousedown', handleMousedown, false);
    context.addEventListener('mousemove', handleMousemove, false);
    context.addEventListener('mouseup', handleMouseup, false);
};

export default { initialize, events, destroy };
